package client;

import client.game.Game;
import java.nio.ByteBuffer;
import shared.client.io.ClientCommand;
import shared.net.protocol.PacketCreator;
import shared.net.protocol.ServerHeader;
import shared.util.PacketIO;
import shared.util.Pair;

/**
 * This class will do the actual handling of the commands typed in by the
 * player.
 *
 * @author Vincent Tsuei
 * @version 2012-11-20
 */
public class ClientCommandHandler {

    /* The client this command handler is associated with */
    private final Client client;

    public ClientCommandHandler(Client client) {
        this.client = client;
    }

    /**
     * Handle commands given by the user.
     *
     * Returns 0 for success, less than 0 for failure Optional string to print
     * to user may be attached.
     */
    public Pair<Integer, String> handleCommand(ClientCommand command, String[] args) {
        try {
            ByteBuffer b;
            byte resp;
            switch (command) {

                // Login with a specified user name.
                case LOGIN:
                    if (!client.isLoggedIn()) {
                        client.sendPacketAndWaitForResponse(
                                PacketCreator.LOGIN.getLoginRequest(args[0]),
                                this, ServerHeader.Send.LOGIN_RESPONSE);
                        b = client.retrieveWaitResponse();
                        resp = b.get();
                        if (resp == 1) {
                            client.setLogin(args[0]);
                            client.getUserIO().write("Login successful!", true);
                            return new Pair(0, null);
                        } else if (resp == 0) {
                            client.getUserIO().write("[ERROR] " + PacketIO.readString(b), true);
                            return new Pair(-1, null);
                        }
                    } else {
                        client.getUserIO().write("Already logged in as " + client.getUserName() + ".", true);
                    }
                    break;

                // Self-explanatory. Exit the program.
                case BYE:
                    System.exit(0); //Exit - Just shutdown the program. We could handle this better. (If we have time later).
                    break;


                // Send a game list request to server, and wait for a reply.
                case LIST_GAMES:
                    client.sendPacketAndWaitForResponse(PacketCreator.LOBBY.getGameListRequest(),
                            this, ServerHeader.Send.GAME_LIST_RESPONSE);
                    b = client.retrieveWaitResponse();
                    resp = b.get();
                    if (resp == 1) {
                        int numGames = b.getInt();
                        if (numGames > 0) {
                            client.getUserIO().write("======= GAME LISTING ======", true);
                            for (int i = 0; i < numGames; i++) {
                                long gameNum = b.getLong();
                                String p1 = PacketIO.readString(b);
                                String p2 = PacketIO.readString(b);
                                client.getUserIO().write(gameNum + "\t" + p1 + "\t" + p2, true);
                            }
                        } else {
                            client.getUserIO().write("No games are currently occuring!", true);
                        }
                        return new Pair(0, null);
                    } else if (resp == 0) {
                        client.getUserIO().write("[ERROR] " + PacketIO.readString(b), true);
                        return new Pair(-1, null);
                    }
                    break;

                // Send a player list request to the server, then wait for a reply.
                case LIST_PLAYERS:
                    client.sendPacketAndWaitForResponse(PacketCreator.LOBBY.getPlayerListRequest(),
                            this, ServerHeader.Send.PLAYER_LIST_RESPONSE);
                    b = client.retrieveWaitResponse();
                    resp = b.get();
                    if (resp == 1) {
                        int numPlayers = b.getInt();
                        if (numPlayers > 0) {
                            client.getUserIO().write("======= PLAYER LISTING ======", true);
                            for (int i = 0; i < numPlayers; i++) {
                                String p = PacketIO.readString(b);
                                client.getUserIO().write(p, true);
                            }
                        } else {
                            client.getUserIO().write("No players are currently logged in!", true);
                        }
                        return new Pair(0, null);
                    } else if (resp == 0) {
                        client.getUserIO().write("[ERROR] " + PacketIO.readString(b), true);
                        return new Pair(-1, null);
                    }
                    break;

                // Either request a game with someone, or accept an invitation from someone.
                case START_GAME:
                    if (!client.isInLobby()) {
                        client.getUserIO().write("You must be in the lobby to start a game", true);
                        return new Pair(-1, null);
                    }

                    client.sendPacketAndWaitForResponse(PacketCreator.LOBBY.PLAY.getNewGameRequest(args[0]),
                            this, ServerHeader.Send.NEW_GAME_RESPONSE);
                    b = client.retrieveWaitResponse();
                    resp = b.get();
                    switch (resp) {
                        case 0:
                            client.getUserIO().write("[ERROR] " + PacketIO.readString(b), true);
                            return new Pair(-1, null);
                        case 1:
                            client.getUserIO().write("Invite successfully sent to " + args[0], true);
                            return new Pair(1, null);
                        case 2:
                            client.getUserIO().write("You have accepted an invitation from " + args[0], true);
                            long gameID = b.getLong();
                            String opponent = PacketIO.readString(b);
                            boolean clientsTurn = b.get() == 1;
                            int[] piles = new int[b.getInt()];
                            for (int i = 0; i < piles.length; i++) {
                                piles[i] = b.getInt();
                            }
                            Game g = new Game(gameID, opponent, piles, client, clientsTurn);
                            client.registerGame(g);

                            client.getUserIO().PrintGameStats();
                            client.getGame().run(); //Transfer control until the game ends.
                            return new Pair(1, null);
                    }
                    break;

                    
                // Used in game - remove [amount to remove] [set number]
                case REMOVE:
                    if (!client.isPlaying()) {
                        client.getUserIO().write("You must be in a game to use this command!", true);
                        return new Pair(-1, null);
                    }
                    int pileNum, removeNum;
                    //Ensure we're getting proper numbers.
                    try {
                        pileNum = Integer.parseInt(args[0]);
                        removeNum = Integer.parseInt(args[1]);
                    } catch (NumberFormatException nfe) {
                        client.getUserIO().write("Please specify integer amount for the set number and amount to remove.", true);
                        return new Pair(-1, null);
                    }
                    client.sendPacketAndWaitForResponse(PacketCreator.IN_GAME.getPlayerMoveRequest(pileNum, removeNum), this, ServerHeader.Send.GAME_UPDATE);
                    b = client.retrieveWaitResponse();
                    resp = b.get();
                    if (resp == 0) {
                        client.getUserIO().write(PacketIO.readString(b), true);
                        return new Pair(-1, null);
                    } else if (resp == 1) {
                        String msg = PacketIO.readString(b);
                        client.getUserIO().write(msg, true);
                        long gameID = b.getLong();
                        boolean clientsTurn = b.get() == 1;
                        int[] piles = new int[b.getInt()];
                        for (int i = 0; i < piles.length; i++) {
                            piles[i] = b.getInt();
                        }
                        client.getGame().updateGame(piles, clientsTurn);
                    }
                    return new Pair(1, null);

                    
                // Send a request to observe a game
                case OBSERVE:
                    if (!client.isInLobby()) {
                        client.getUserIO().write("You must be in the lobby to observe a game", true);
                        return new Pair(-1, null);
                    }
                    long gid;
                    try {
                        gid = Long.parseLong(args[0]);
                    } catch (NumberFormatException nfe) {
                        client.getUserIO().write("Please enter a proper game ID.", true);
                        return new Pair(-1, null);
                    }
                    client.sendPacketAndWaitForResponse(
                            PacketCreator.LOBBY.OBSERVE.getObserveGameRequest(gid),
                            this, ServerHeader.Send.OBSERVE_GAME_RESPONSE);
                    b = client.retrieveWaitResponse();
                    resp = b.get();
                    if (resp == 0) {
                        client.getUserIO().write(PacketIO.readString(b), true);
                        return new Pair(-1, null);
                    } else {
                        client.setObserve();
                        long gameID = b.getLong();
                        String p1 = PacketIO.readString(b);
                        String p2 = PacketIO.readString(b);
                        String pturn = PacketIO.readString(b);
                        int[] set = new int[b.getInt()];
                        for (int i = 0; i < set.length; i++) {
                            set[i] = b.getInt();
                        }

                        client.getUserIO().write("", true);
                        client.getUserIO().write("Game " + gameID + " | [" + p1 + "] vs. [" + p2 + "]", true);
                        client.getUserIO().write("It is currently " + pturn + "'s turn:\t", false);
                        for (int i : set) {
                            client.getUserIO().write(i + " ", false);
                        }
                        client.getUserIO().write("", true);

                    }
                    break;

                // Send a request to unobserve a game.
                case UNOBSERVE:
                    if (!client.isObserving()) {
                        client.getUserIO().write("You are not observing a game!", true);
                        return new Pair(-1, null);
                    }
                    client.sendPacketAndWaitForResponse(
                            PacketCreator.IN_GAME.getUnobserveGameRequest(Long.parseLong(args[0])),
                            this, ServerHeader.Send.OBSERVE_GAME_RESPONSE);
                    b = client.retrieveWaitResponse();
                    resp = b.get();
                    if (resp == 0) {
                        client.getUserIO().write(PacketIO.readString(b), true);
                        return new Pair(-1, null);
                    } else {
                        client.setLobby();
                        client.setExpectUnobserve();
                    }
                    break;
                    
                // Sometimes we call this method outside of the main user IO loop.
                // In this case, we want the HELP command to be handled.
                case HELP:
                    client.getUserIO().PrintMenu();
                    break;
                    
                // Tell us if something went wrong.
                default:
                    throw new RuntimeException("Unhandled ClientCommand: " + command);
            }
            return null; //Remove after.
            
        } catch (InterruptedException ex) {
            return new Pair<Integer, String>(-1, "Interrupted");
        }
    }
}
